﻿#pragma warning(disable : 4996)
#include "resource_tool.h"
#define FAST_FLTK_INLCUDE
#define HIDE_CMD
#include "FLTK_tool.h"
#include "FL/Fl_Check_Button.H"
#include "FL/Fl_Choice.H"
#include "base64_easy.h"
#include <unordered_map>
#include <fstream>
#include "winProgress.h"
#include "ThreadPool.h"
#include "cryptopp_aes_help.h"
using std::string;
constexpr int replace_file_len = 11451;

bool allow = 1;
string aes_key;
std::unordered_map<char, char> rules = {
	{'A','w'},{'B','d'},{'C','G'},{'D','6'},{'E','N'},{'F','f'},{'G','C'},{'H','l'},{'I','r'},{'J','m'},{'K','k'},{'L','c'},{'M','i'},{'N','9'},{'O','u'},{'P','S'},{'Q','q'},{'R','Q'},{'S','#'},{'T','s'},{'U','L'},{'V','U'},{'W','1'},{'X','h'},{'Y','j'},{'Z','z'},{'a','&'},{'b','E'},{'c','T'},{'d','0'},{'e','n'},{'f','e'},{'g','+'},{'h','F'},{'i','A'},{'j','x'},{'k','Y'},{'l','8'},{'m','X'},{'n','I'},{'o','K'},{'p','%'},{'q','v'},{'r','b'},{'s','^'},{'t','J'},{'u','3'},{'v','g'},{'w','Z'},{'x','a'},{'y','4'},{'z','2'},{'0','W'},{'1','D'},{'2','t'},{'3','5'},{'4','R'},{'5','V'},{'6','H'},{'7','M'},{'8','B'},{'9','7'},{'+','y'},{'/','!'},{'=', '_'}
};
std::unordered_map<char, char> rules2;

static std::wstring AtoW(const std::string& str)
{
	int wcLen = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
	std::wstring newBuf;
	newBuf.resize(wcLen);
	MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, (LPWSTR)newBuf.c_str(), wcLen);
	return newBuf;
}

// 通过map映射替换string中的每个字符
std::string replace_all_from_map(const std::string& input, const std::unordered_map<char, char>& rules) {
	std::string result = input; // 复制输入字符串

	// 遍历字符串中的每个字符
	for (char& c : result) {
		// 查找当前字符在哈希表中的映射
		auto it = rules.find(c);
		// 如果找到了映射，则替换字符
		if (it != rules.end()) {
			c = it->second;
		}
	}
	return result;
}

template<typename arg1, typename arg2>
std::unordered_map<arg2, arg1> reverseMap(const std::unordered_map<arg1, arg2>& originalMap) {
	std::unordered_map<arg2, arg1> reversedMap;
	for (const auto& pair : originalMap) {
		// 交换键和值
		reversedMap[pair.second] = pair.first;
	}
	return reversedMap;
}

bool file_lock_unlock(std::string path) {
	using namespace std;
	fstream f(path, ios::in | ios::out | ios::binary);
	if (!f.is_open())
		return 0;
	f.seekg(0, ios::end);
	long long s = f.tellg();
	//cout << "大小：" << s << endl;
	f.seekg(0, ios::beg);
	vector<char> buffer;
	if (s < replace_file_len)buffer.resize(s);
	else buffer.resize(replace_file_len);
	f.read(buffer.data(), buffer.size());
	reverse(buffer.begin(), buffer.end());

	for (auto& i : buffer) {
		i = 255 - i;
	}
	//for (int i = 0; i < buffer.size(); i += 2) {
	//	buffer[i] = 255 - buffer[i];
	//}
	f.seekp(0);
	f.write(buffer.data(), buffer.size());
	f.close();
	return 1;
}

void cb1(Fl_Input* input_1, Fl_Double_Window* win_1) {
	string v = input_1->value();
	if (v == "laull" || v == "LAULL") {
		allow = 1;
		win_1->hide();
	}
	else {
		FMB_utf8("", "密码不正确", MB_OK | MB_ICONERROR);
		quick_exit(0);
	}
}

bool check_aes_key(string key, int key_type) {
	switch (key_type)
	{
	case 0:
		aes_key = make_aes_hash_key(key);
		return true;
	case 1:
		size_t l = key.size();
		if (l == 16 || l == 24 || l == 32) {
			aes_key = key;
			break;
		}
		else
			return false;
	}
}

//置顶本窗口
inline void win_top_show() {
	HWND hWnd = GetForegroundWindow();
	SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 100, 100, SWP_NOMOVE | SWP_NOSIZE);
}

int class_name_count = 0;

auto get_class_name() {
#ifdef UNICODE
	const std::wstring ss = L"LAULL_PROGRESS_";
	return (ss + std::to_wstring(class_name_count++));
#else
	const std::string ss = "LAULL_PROGRESS_";
	return (ss + std::to_string(class_name_count++));
#endif // UNICODE
}

int main(int argc, char** argv) {
	int lock_type = 0;
	string start = "";

	if (argc > 1) start = argv[1];

	rules2 = reverseMap(rules);

	Fl::scheme("gtk+");
	//Fl::background(123, 181, 214);
	//auto win_1 = new Fl_Double_Window(400, 300, 300, 300, "???");
	//auto box_1 = new Fl_Box(100, 100, 100, 30, "pass word");
	//auto input_1 = new Fl_Input(100, 150, 100, 30);
	//auto button_1 = new Fl_Button(125, 200, 50, 30, "Done");
	//fcallback(button_1, cb1, input_1, win_1);
	//clear_buttons_focus(button_1);
	//win_1->end();
	//win_1->show(1, (char**)argv[0]);

	//Fl::run();
	//delete win_1, box_1, input_1, button_1;
	if (allow) {
		auto win = new Fl_Double_Window(400, 250, 400, 400, " 文件加解密@laull V3.0");
		auto in1 = new Fl_Input(30, 30, 250, 50);
		auto b1 = new Fl_Button(300, 30, 80, 25, "选择文件夹");
		auto b2 = new Fl_Button(300, 55, 80, 25, "选择文件");
		auto b3 = new Fl_Button(310, 130, 60, 30, "清除输入");
		auto b4 = new Fl_Button(310, 170, 60, 30, "帮助...");
		auto choice1 = new Fl_Check_Button(30, 90, 70, 30, "添加后缀名混淆");
		auto in2 = new Fl_Input(200, 90, 100, 30);
		auto question1 = new Fl_Button(310, 90, 30, 30, "?");
		auto choice2 = new Fl_Choice(100, 130, 200, 30, "作用范围");
		auto choice_dir = new Fl_Choice(100, 170, 200, 30, "处理...");
		//AES
		auto choice_locktype = new Fl_Choice(100, 210, 200, 30, "加密方式");
		auto choice_aeskeytype = new Fl_Choice(100, 250, 200, 30, "密钥类型");
		auto text_key = new Fl_Box(25, 295, 40, 30, "密钥:");
		auto in_key = new Fl_Input(65, 290, 310, 40);
		auto choice_thread = new Fl_Choice(310, 210, 70, 30);

		auto b_do = new Fl_Button(90, 340, 100, 40, "加密");
		auto b_undo = new Fl_Button(210, 340, 100, 40, "解密");
		in1->value(start.c_str());
		choice2->add("仅仅这个文件夹内");
		choice2->add("这个文件夹下的所有文件夹");
		choice2->value(1);
		choice_dir->add("只处理文件");
		choice_dir->add("只处理文件夹");
		choice_dir->add("处理文件和文件夹");
		choice_dir->value(2);
		choice_locktype->add("快速加密(无密钥)");
		choice_locktype->add("AES-CBC加密(有密钥)");
		choice_locktype->value(0);
		in_key->deactivate();
		choice_aeskeytype->deactivate();
		text_key->deactivate();
		
		choice_aeskeytype->add("SHA256密钥算法");
		choice_aeskeytype->add("原密钥(需为16或24或32位)");
		choice_aeskeytype->value(0);

		choice_thread->add("单线程");
		choice_thread->add("2线程");
		choice_thread->add("4线程");
		choice_thread->add("8线程");
		choice_thread->add("16线程");
		choice_thread->add("32线程");
		choice_thread->value(4);

		clear_buttons_focus(b1, choice1, question1, choice2, b_do, b_undo, choice_dir, b2, b3, b4, choice_locktype, choice_aeskeytype, text_key, choice_thread);
		Fl::add_handler([](int event) -> int { return event == FL_SHORTCUT; }); // 忽略windows窗口Esc退出, 但禁止了所有快捷键
		fcallback(b1, [&in1] {
			const char* path = input_dir_ui();
			if (path != nullptr)  in1->value(GbkToUtf8(path).c_str());
			});
		fcallback(b2, [&in1] {
			char path[1024]{};
			if (input_file_ui((TCHAR*)AtoW(path).c_str())) in1->value(GbkToUtf8(path).c_str());
			});
		fcallback(b3, [&in1, &in2, &in_key] {
			in1->value("");
			in2->value("");
			in_key->value("");
		});
		fcallback(b4, [] {
			FMB_utf8("--HELP--@LAULL says:", "帮助\n简单加密你的文件和文件夹\n输入框可以直接将文件或文件夹拖动输入\n无法识别带有表情包如😀的文件或者文件名\n快速加密后的名称会开头带有$\nAES加密会开头带#$\n请勿在加密文件打开的时候加密，会造成文件损坏！", MB_OK | MB_ICONINFORMATION);
			});
		fcallback(question1, [] {FMB_utf8("帮助---添加后缀名混淆", "就是在文件末尾加入一个混淆文件名\n（输入时不包含 . ）比如exe， dat", MB_OK); });

		fcallback(choice_locktype, [&] {
			if (choice_locktype->value() == 0) {
				in_key->deactivate();
				choice_aeskeytype->deactivate();
				text_key->deactivate();
				lock_type = 0;
			}
			else {
				in_key->activate();
				choice_aeskeytype->activate();
				text_key->activate();
				lock_type = 1;
			}
			});

		//加密
		fcallback(b_do, [&] {
			if (lock_type == 1 && !check_aes_key(in_key->value(), choice_aeskeytype->value())) {
				char sstr__[100];
				sprintf(sstr__, "AES密钥错误\n请检查密钥是否位数符合要求\n当前密钥长度：%llu", strlen(in_key->value()));
				FMB_utf8("加密", sstr__, MB_OK | MB_ICONERROR);
				return;
			}
			ThreadPool Pool(pow(2, choice_thread->value()));
			string path = Utf8ToGbk(in1->value());
			int dir_file_pattern = choice_dir->value();
			long failed_count = 0, failed_count2 = 0, all_file_count = 0, all_dir_count = 0, ignore_count = 0, ignore_count2 = 0;
			bool input_file = 0;
			//检查文件夹存在
			std::filesystem::path tem_p(path);
			if (!std::filesystem::exists(tem_p)) {
				string t = path;
				if (is_str_gbk(t.c_str()))
					t = GbkToUtf8(t);
				t = "文件夹/文件不存在:\n" + t;
				FMB_utf8("加密", t.c_str(), MB_OK | MB_ICONERROR);
				return;
			}
			if (!std::filesystem::is_directory(path)) {
				input_file = 1;
			}
			if (FMB_utf8("加密", "确认加密？\n已加密的会被忽略", MB_OKCANCEL | MB_ICONQUESTION) != 1)  return;
			// ———————处理文件
			if (dir_file_pattern == 0 || dir_file_pattern == 2) {
				WinProgress w_jia(NULL, get_class_name().c_str(), TEXT(""), 1, 0, FALSE);
				w_jia.SetText(TEXT("加载中"));
				std::vector<string> paths;
				if (input_file) paths = { path };
				else {
					if (choice2->value() == 0)  paths = get_files_no_dir(path);
					else paths = get_files_all(path);
				}
				w_jia.SetTotal(paths.size());
				w_jia.SetText(TEXT("加密文件中"));
				string exc = ".";
				if (strlen(in2->value()) > 0 && choice1->value()) exc += Utf8ToGbk(in2->value());
				else exc = "";
				all_file_count = paths.size();
				
				std::vector<std::future<void>> vrs;
				for (auto& i : paths) {
					vrs.push_back(
						Pool.enqueue([&] {
						string tem, tem_path, tem_name;
						tem_path = split_file_last_path(i) + "\\";
						tem_name = split_file_name(i);
						if (tem_name.front() == '$' || tem_name.substr(0, 2)=="#$") {
							ignore_count++; all_file_count--;
							return;
						}
						if (lock_type == 0) {
							tem_name = "$" + replace_all_from_map(base64_encode(tem_name), rules);
							tem = tem_path + tem_name + exc;
							if (rename(i.c_str(), tem.c_str()) != 0) {
								failed_count++;
								return;
							}
							if (file_lock_unlock(tem) == 0) {
								failed_count++;
								return;
							}
						}
						else if (lock_type == 1) {
							tem_name = "#$" + replace_all_from_map(base64_encode(tem_name), rules);
							tem = tem_path + tem_name + exc;
							if (rename(i.c_str(), tem.c_str()) != 0) {
								failed_count++;
								return;
							}
							if (AES_encode_file(tem, aes_key) == 0) {
								failed_count++;
								return;
							}
						}
						})
					);
				}
				for (auto& f : vrs) {
					f.get();
					w_jia.Addprogress(1);
				}
				w_jia.Close();
			}
			// ———————处理文件夹
			if ((dir_file_pattern == 1 || dir_file_pattern == 2) && input_file == 0) {
				WinProgress w_jia2(NULL, get_class_name().c_str(), TEXT(""), 1, 0, FALSE);
				w_jia2.SetText(TEXT("加载中"));
				std::vector<string> dirs;
				if (choice2->value() == 0)  dirs = get_dirs_only(path);
				else dirs = get_dirs_all(path);
				// 这里翻转是因为处理上级目录时会导致下级目录路径改变，因此从下级开始
				std::reverse(dirs.begin(), dirs.end());
				w_jia2.SetTotal(dirs.size());
				w_jia2.SetText(TEXT("加密文件夹中"));

				string tem, tem_path, tem_name;
				all_dir_count = dirs.size();
				for (auto& i : dirs) {
					tem_path = split_file_last_path(i) + "\\";
					tem_name = split_file_name(i);
					if (tem_name.front() == '$') {
						ignore_count2++; all_dir_count--;
						continue;
					}
					tem_name = "$" + replace_all_from_map(base64_encode(tem_name), rules);
					tem = tem_path + tem_name;
					//std::cout << tem << std::endl;
					if (rename(i.c_str(), tem.c_str()) != 0) failed_count2++;
					w_jia2.Addprogress(1);
				}
				w_jia2.Close();
			}

			char out[256];
			sprintf(out, "加密完成---\n%s\n共加密了%ld个文件\n%ld个文件夹\n失败%ld个文件\n失败%ld个文件夹\n忽略%ld个文件\n忽略%ld个文件夹",
				GbkToUtf8(path).c_str(), all_file_count, all_dir_count, failed_count, failed_count2, ignore_count, ignore_count2);
			FMB_utf8("加密", out, MB_OK | MB_ICONMASK);
		});

		//解密
		fcallback(b_undo, [&] {
			if (lock_type == 1 && !check_aes_key(in_key->value(), choice_aeskeytype->value())) {
				char sstr__[100];
				sprintf(sstr__, "AES密钥错误\n请检查密钥是否位数符合要求\n当前密钥长度：%llu", strlen(in_key->value()));
				FMB_utf8("解密", sstr__, MB_OK | MB_ICONERROR);
				return;
			}
			ThreadPool Pool(pow(2, choice_thread->value()));
			string path = Utf8ToGbk(in1->value());
			int dir_file_pattern = choice_dir->value();
			long failed_count = 0, failed_count2 = 0, all_file_count = 0, all_dir_count = 0, ignore_count = 0, ignore_count2 = 0, aes_file_failed = 0;
			bool input_file = 0;
			//检查文件夹存在
			std::filesystem::path tem_p(path);
			if (!std::filesystem::exists(tem_p)) {
				string t = path;
				if (is_str_gbk(t.c_str()))
					t = GbkToUtf8(t);
				t = "文件夹/文件不存在:\n" + t;
				FMB_utf8("解密", t.c_str(), MB_OK | MB_ICONERROR);
				return;
			}
			if (!std::filesystem::is_directory(path)) {
				input_file = 1;
			}
			if (FMB_utf8("解密", "确认解密？\n未按加密格式的会被忽略", MB_OKCANCEL | MB_ICONQUESTION) != 1)  return;

			// ———————处理文件
			if (dir_file_pattern == 0 || dir_file_pattern == 2) {
				WinProgress w_jie(NULL, get_class_name().c_str(), TEXT(""), 1, 0, FALSE);
				w_jie.SetText(TEXT("加载中"));
				std::vector<string> paths;
				if (input_file) paths = { path };
				else {
					if (choice2->value() == 0)  paths = get_files_no_dir(path);
					else paths = get_files_all(path);
				}
				w_jie.SetTotal(paths.size());
				w_jie.SetText(TEXT("解密文件中"));

				all_file_count = paths.size();

				std::vector<std::future<void>> vrs;
				for (auto& i : paths) {
					vrs.push_back(Pool.enqueue([&] {
						try {
						string tem, tem_path, tem_name;
						tem_path = split_file_last_path(i) + "\\";
						tem_name = split_file_exc_path(split_file_name(i));
						if (tem_name.front() == '$' && lock_type == 0) {
							tem_name.erase(0, 1);
							tem = tem_path + base64_decode(replace_all_from_map(tem_name, rules2));
							if (rename(i.c_str(), tem.c_str()) != 0) {
								failed_count++;
								return;
							}
							if (file_lock_unlock(tem) == 0) {
								failed_count++;
								return;
							}
						}
						else if (tem_name.substr(0, 2) == "#$" && lock_type == 1) {
							tem_name.erase(0, 2);
							tem = tem_path + base64_decode(replace_all_from_map(tem_name, rules2));
							if (AES_decode_file(i, aes_key) == 0) {
								failed_count++;
								aes_file_failed++;
								return;
							}
							if (rename(i.c_str(), tem.c_str()) != 0) {
								failed_count++;
								return;
							}
						}
						else {
							ignore_count++; 
							all_file_count--; 
							return;
						}
						
					}
					catch (...) { failed_count++; }
						})
					);
				}
				for (auto& f : vrs) {
					f.get();
					w_jie.Addprogress(1);
				}
				w_jie.Close();
			}
			// ———————处理文件夹
			if ((dir_file_pattern == 1 || dir_file_pattern == 2) && input_file == 0) {
				WinProgress w_jie2(NULL, get_class_name().c_str(), TEXT(""), 1, 0, FALSE);
				w_jie2.SetText(TEXT("加载中"));
				std::vector<string> dirs;
				if (choice2->value() == 0)  dirs = get_dirs_only(path);
				else dirs = get_dirs_all(path);
				// 这里翻转是因为处理上级目录时会导致下级目录路径改变，因此从下级开始
				std::reverse(dirs.begin(), dirs.end());

				w_jie2.SetTotal(dirs.size());
				w_jie2.SetText(TEXT("解密文件夹中"));

				string tem, tem_path, tem_name;
				all_dir_count = dirs.size();
				for (auto& i : dirs) {
					try {
						tem_path = split_file_last_path(i) + "\\";
						tem_name = split_file_name(i);
						//std::cout << tem_path << '\n' << tem_name << '\n';
						if (tem_name.front() == '$')
							tem_name.erase(0, 1);
						else {
							ignore_count2++; all_dir_count--; continue;
						}
						tem = tem_path + base64_decode(replace_all_from_map(tem_name, rules2));
						//std::cout << tem << std::endl;
						if (rename(i.c_str(), tem.c_str()) != 0)  failed_count2++;
					}
					catch (...) { failed_count2++; }
					w_jie2.Addprogress(1);
				}
				w_jie2.Close();
			}
			if (aes_file_failed > 0) {
				char out_2[100];
				sprintf(out_2, "注意：AES的密钥错误\n因为密钥错误无法解密的文件个数：%ld", aes_file_failed);
				FMB_utf8("AES解密失败：密钥错误", out_2, MB_OK | MB_ICONERROR);
			}

			char out[256];
			sprintf(out, "解密完成---\n%s\n共解密了%ld个文件\n%ld个文件夹\n失败%ld个文件\n失败%ld个文件夹\n忽略%ld个文件\n忽略%ld个文件夹",
				GbkToUtf8(path).c_str(), all_file_count, all_dir_count, failed_count, failed_count2, ignore_count, ignore_count2);
			FMB_utf8("解密", out, MB_OK | MB_ICONMASK);

		});
		win->show(1, (char**)argv[0]);
		//win_top_show();
		return Fl::run();
	}
}